package com.cmc.cloud.cmclink.doc.service.impl.si;

import static com.cmc.cloud.cmclink.doc.api.document.emums.ConfirmNoticeEnum.CANCEL_TELEX_SEAWAY;
import static com.cmc.cloud.cmclink.doc.api.document.emums.ConfirmNoticeEnum.MCN;
import static com.cmc.cloud.cmclink.doc.api.document.emums.ConfirmNoticeEnum.TELEX_SEAWAY;
import static com.cmc.cloud.cmclink.doc.api.document.emums.SiChangeStatusEnum.SI_AMENDMENT_APPROVED;
import static com.cmc.cloud.cmclink.doc.api.document.emums.SiChangeStatusEnum.SI_AMENDMENT_PROCESSING;
import static com.cmc.cloud.cmclink.doc.api.document.emums.SiOperateTypeEnum.OPERATE_SI_THREE;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.ManifestErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.mapper.BlnoBlPrintLogMapper;
import com.cmc.cloud.cmclink.doc.mapper.BlnoVslvoyMapper;
import com.cmc.cloud.cmclink.doc.mapper.ConfirmNoticeDetailMapper;
import com.cmc.cloud.cmclink.doc.service.ApplySaveOrUpdateService;
import com.cmc.cloud.cmclink.doc.service.BlnoCntrService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoBlPrintLogService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoCargoService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoPaymentService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoScnService;
import com.cmc.cloud.cmclink.doc.service.so.AttachmentService;
import com.cmc.cloud.cmclink.doc.service.so.RequirementsService;
import com.cmc.cloud.cmclink.doc.service.so.SoService;
import com.cmc.cloud.cmclink.doc.util.CostChargeUtil;
import com.cmc.cloud.cmclink.doc.vo.blnocargovo.BlnoCargoUpdateReqVO;
import com.cmc.cloud.cmclink.doc.vo.blnopaymentvo.BlnoPaymentUpdateReqVO;
import com.cmc.cloud.cmclink.doc.vo.blnoscnvo.BlnoScnUpdateReqVO;
import com.cmc.cloud.cmclink.doc.vo.blnovo.BlnoBaseInfoVO;
import com.cmc.cloud.cmclink.doc.vo.blnovo.BlnoInfoVO;
import com.cmc.cloud.cmclink.doc.vo.blnovslvoyvo.BlnoVslvoyUpdateReqVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.attachmentvo.AttachmentUpdateReqVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.requirementsvo.RequirementsUpdateReqVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovslvoyvo.SoVslvoyUpdateReqVO;
import com.cmes.framework.common.pojo.CommonResult;
import com.cmes.framework.ex.Assertion;
import com.cmes.platform.system.nuc.NucApi;
import com.cmes.platform.system.nuc.dto.UserProfileRespDto;
import liquibase.pro.packaged.L;
import org.apache.commons.collections.CollectionUtils;
import org.apache.tika.utils.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.constants.CommonConstants;
import com.cmc.cloud.cmclink.doc.constants.NoticeRemarkConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.ConfirmNoticeErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.BlnoVslvoyConvert;
import com.cmc.cloud.cmclink.doc.convert.ConfirmNoticeFcnConvert;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.mapper.BlnoMapper;
import com.cmc.cloud.cmclink.doc.mapper.ConfirmNoticeMapper;
import com.cmc.cloud.cmclink.doc.mapper.OperateLogMapper;
import com.cmc.cloud.cmclink.doc.rpcservice.CustomerRpcService;
import com.cmc.cloud.cmclink.doc.rpcservice.NucTransferService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoVslvoyService;
import com.cmc.cloud.cmclink.doc.service.si.ConfirmNoticeFcnService;
import com.cmc.cloud.cmclink.doc.service.si.ConfirmNoticeService;
import com.cmc.cloud.cmclink.doc.util.VslvoyUtil;
import com.cmc.cloud.cmclink.doc.vo.blnovslvoyvo.BlnoVslvoyRespVO;
import com.cmc.cloud.cmclink.doc.vo.confirmnoticefcnvo.ConfirmNoticeFcnRespVO;
import com.cmc.cloud.cmclink.doc.vo.confirmnoticevo.*;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.PageResult;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cmes.framework.security.core.LoginUser;
import com.cmes.framework.security.core.util.SecurityFrameworkUtils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;

/**
 * 确认装货港更改通知 Service 实现类
 *
 * @author 集运管理员
 */
@Service
@Validated
public class ConfirmNoticeServiceImpl implements ConfirmNoticeService {

    private final static String SI_OPERATE_TYPE = "SI_Operate";
    @Resource
    private ConfirmNoticeMapper confirmNoticeMapper;
    @Resource
    private BlnoVslvoyService blnoVslvoyService;
    @Resource
    private ConfirmNoticeFcnService confirmNoticeFcnService;
    @Resource
    private BlnoMapper blnoMapper;
    @Resource
    private OperateLogMapper operateLogMapper;
    @Resource
    private CustomerRpcService nameTransferService;
    @Resource
    private NucTransferService nucTransferService;
    @Resource
    private ConfirmNoticeDetailMapper confirmNoticeDetailMapper;
    @Resource
    private BlnoPaymentService blnoPaymentService;
    @Resource
    private AttachmentService attachmentService;
    @Resource
    private RequirementsService requirementsService;
    @Resource
    private BlnoScnService blnoScnService;
    @Resource
    private BlnoCargoService blnoCargoService;
    @Resource
    private BlnoCntrService blnoCntrService;
    @Resource
    private NucApi nucApi;
    @Resource
    private CostChargeUtil costChargeUtil;
    @Resource
    private VslvoyUtil vslvoyUtil;

    @Resource
    ThreadPoolTaskExecutor taskExecutor;

    @Resource
    private BlnoBlPrintLogService blnoBlPrintLogService;
    @Resource
    private BlnoBlPrintLogMapper blnoBlPrintLogMapper;

    @Resource
    private ApplySaveOrUpdateService applySaveOrUpdateService;
    @Resource
    private BlnoVslvoyMapper blnoVslvoyMapper;
    @Resource
    private SoService soService;



    @Override
    public ConfirmNoticeDetailRespVO getConfirmNotice(Long id) {
        ConfirmNoticeDO confirmNotice = confirmNoticeMapper.selectById(id);
        String notice = confirmNotice.getNotice();
        ConfirmNoticeDetailRespVO resp = null;
        if (ConfirmNoticeEnum.FCN.getValue().equals(notice)) {
            resp = dealWithNoticeFcn(confirmNotice);
        }

        if (TELEX_SEAWAY.getValue().equals(notice)) {
            resp = dealWithNoticeTelex(confirmNotice);
        }

        if (ConfirmNoticeEnum.CANCEL_TELEX_SEAWAY.getValue().equals(notice)) {
            resp = dealWithNoticeCancelTelex(confirmNotice);
        }

        if (ConfirmNoticeEnum.COD.getValue().equals(notice)||ConfirmNoticeEnum.MCN.getValue().equals(notice)
                ||ConfirmNoticeEnum.SIC.getValue().equals(notice)) {
            resp = dealWithNoticeCod(confirmNotice);
        }
        return resp;
    }

    private ConfirmNoticeDetailRespVO dealWithNoticeFcn(ConfirmNoticeDO confirmNotice) {
        Long blnoId = confirmNotice.getBlnoId();
        Long noticeId = confirmNotice.getId();
        List<BlnoVslvoyDO> blnoVslvoyDoList = blnoVslvoyService.getBlnoVslvoyBySiId(blnoId);
        List<BlnoVslvoyRespVO> vslvoyRespList = BlnoVslvoyConvert.INSTANCE.convertList(blnoVslvoyDoList);
        List<ConfirmNoticeFcnDO> noticeFcnOriginalList =
                confirmNoticeFcnService.getFcnByNoticeId(noticeId, FrtCalTypesEnum.ORIGINAL.getValue());
        List<ConfirmNoticeFcnRespVO> originalRespList =
                ConfirmNoticeFcnConvert.INSTANCE.convertList(noticeFcnOriginalList);
        List<ConfirmNoticeFcnDO> noticeFcnReCalculateList =
                confirmNoticeFcnService.getFcnByNoticeId(noticeId, FrtCalTypesEnum.RECALCULATE.getValue());
        List<ConfirmNoticeFcnRespVO> recalculateRespList =
                ConfirmNoticeFcnConvert.INSTANCE.convertList(noticeFcnReCalculateList);
        ConfirmNoticeDetailFcnRespVO fcnResp = new ConfirmNoticeDetailFcnRespVO();

        // 获取供应商信息
        Set<String> codes = noticeFcnReCalculateList.stream().map(ConfirmNoticeFcnDO::getPayer).filter(Objects::nonNull)
                .filter(name -> !name.isEmpty()).collect(Collectors.toSet());
        Set<String> codes2 = noticeFcnReCalculateList.stream().map(ConfirmNoticeFcnDO::getPayer)
                .filter(Objects::nonNull).filter(name -> !name.isEmpty()).collect(Collectors.toSet());
        codes.addAll(codes2);
        Map<String, String> companyNameMap = nameTransferService.getCompanyNameMap(new ArrayList<>(codes));
        originalRespList.forEach(resp -> resp.setPayerName(companyNameMap.getOrDefault(resp.getPayer(), null)));
        recalculateRespList.forEach(resp -> resp.setPayerName(companyNameMap.getOrDefault(resp.getPayer(), null)));

        fcnResp.setBlnoVslvoyDOList(vslvoyRespList);
        fcnResp.setNoticeFcnOriginalList(originalRespList);
        fcnResp.setNoticeFcnReCalculateList(recalculateRespList);
        ConfirmNoticeDetailRespVO detailResp = new ConfirmNoticeDetailRespVO();
        detailResp.setConfirmNoticeDetailFcnRespVO(fcnResp);
        return detailResp;
    }

    private ConfirmNoticeDetailRespVO dealWithNoticeTelex(ConfirmNoticeDO confirmNotice) {
        ConfirmNoticeDetailRespVO confirmNoticeDetailRespVO = new ConfirmNoticeDetailRespVO();
        confirmNoticeDetailRespVO.setConfirmNoticeTelexRespVO(getTelexResp(confirmNotice));
        return confirmNoticeDetailRespVO;
    }

    private ConfirmNoticeDetailRespVO dealWithNoticeCancelTelex(ConfirmNoticeDO confirmNotice) {
        ConfirmNoticeDetailRespVO confirmNoticeDetailRespVO = new ConfirmNoticeDetailRespVO();
        confirmNoticeDetailRespVO.setConfirmNoticeCancelTelexRespVO(
                BeanUtil.copyProperties(getTelexResp(confirmNotice), ConfirmNoticeCancelTelexRespVO.class));
        return confirmNoticeDetailRespVO;
    }

    private ConfirmNoticeTelexRespVO getTelexResp(ConfirmNoticeDO confirmNotice) {
        ConfirmNoticeTelexRespVO noticeTelexRespVO;
        if (TELEX_SEAWAY.getValue().equals(confirmNotice.getNotice())) {
            noticeTelexRespVO = new ConfirmNoticeTelexRespVO();
        } else {
            noticeTelexRespVO = new ConfirmNoticeCancelTelexRespVO();
        }
        BlnoDO blnoDO = blnoMapper.selectById(confirmNotice.getBlnoId());
        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        noticeTelexRespVO.setBlNo(blnoDO.getBlNo());
        noticeTelexRespVO.setId(confirmNotice.getId());
        noticeTelexRespVO.setNoticeContent(confirmNotice.getNoticeContent());
        return noticeTelexRespVO;
    }

    private ConfirmNoticeDetailRespVO dealWithNoticeCod(ConfirmNoticeDO confirmNotice) {
        ConfirmNoticeDetailRespVO confirmNoticeDetailRespVO = new ConfirmNoticeDetailRespVO();

        BlnoDO blnoDO = blnoMapper.selectById(confirmNotice.getBlnoId());

        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        BlnoInfoVO newBlnoInfoVO = this.getDetail(blnoDO.getId());

        List<BlnoDO> blnoDos = blnoMapper.selectList(new LambdaQueryWrapper<BlnoDO>()
                .eq(BlnoDO::getId, blnoDO.getOldBlnoId())
        );
        BlnoInfoVO oldBlnoInfoVO = this.getDetail(blnoDos.get(0).getId());

        ConfirmNoticeCodRespVO confirmNoticeCodRespVO = new ConfirmNoticeCodRespVO();
        confirmNoticeCodRespVO.setOldBlnoInfoVO(oldBlnoInfoVO);
        confirmNoticeCodRespVO.setNewBlnoInfoVO(newBlnoInfoVO);

        List<ConfirmNoticeDetailDO> confirmNoticeDetailDos = confirmNoticeDetailMapper.selectList(new LambdaQueryWrapperX<ConfirmNoticeDetailDO>().eq(ConfirmNoticeDetailDO::getNoticeId, confirmNotice.getId()));
        confirmNoticeDetailDos.forEach(n->{
            if(blnoDO.getPolCode().equals(n.getCurrentPort())){
                confirmNoticeCodRespVO.setExApproved(n.getNoticeStatus());
            }else if(blnoDO.getPodCode().equals(n.getCurrentPort())){
                confirmNoticeCodRespVO.setImApproved(n.getNoticeStatus());
            }
        });
        confirmNoticeDetailRespVO.setConfirmNoticeCodRespVO(confirmNoticeCodRespVO);
        return confirmNoticeDetailRespVO;
    }

    private ConfirmNoticeDetailRespVO dealWithNoticeMcn(ConfirmNoticeDO confirmNotice) {
        return null;
    }

    @Override
    public PageResult<ConfirmNoticeRespVO> getConfirmNoticePage(ConfirmNoticePageReqVO pageReqVO, String exImType) {
        IPage<ConfirmNoticeRespVO> page = Page.of(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        String monthFrom = pageReqVO.getMonthFrom();
        if (!StringUtils.isEmpty(monthFrom)) {
            monthFrom = monthFrom + CommonConstants.DATE_START;
        }
        String monthTo = pageReqVO.getMonthTo();
        if (!StringUtils.isEmpty(monthTo)) {
            monthTo = monthTo + CommonConstants.DATE_END;
        }
        pageReqVO.setMonthFrom(monthFrom);
        pageReqVO.setMonthTo(monthTo);
        if(!StringUtils.isEmpty(pageReqVO.getApplyUser())){
            try {
                CommonResult<UserProfileRespDto> userProfileByName = nucApi.getUserProfileByName(pageReqVO.getApplyUser());
                UserProfileRespDto checkedData = userProfileByName.getCheckedData();
                if(checkedData!=null){
                    pageReqVO.setApplyUser(String.valueOf(checkedData.getId()));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        IPage<ConfirmNoticeRespVO> pageResult;
        if (ExImTypeEnum.IM.getValue().equals(exImType)) {
            pageResult = confirmNoticeMapper.getConfirmNoticePage(page, pageReqVO);
        } else {
            pageResult = confirmNoticeMapper.getConfirmNoticeExPage(page, pageReqVO);
        }

        // 设置进口/出口船名航次
        List<ConfirmNoticeRespVO> respList = pageResult.getRecords();
        if (!CollectionUtils.isEmpty(respList)) {
            Set<Long> ids = respList.stream().map(ConfirmNoticeRespVO::getBlnoId).collect(Collectors.toSet());
            List<BlnoVslvoyDO> blnoVslvoyDOList = blnoVslvoyService.getBlnoVslvoyBySiIdList(ids);
            Map<Long, List<BlnoVslvoyDO>> vslvoyListMap =
                    blnoVslvoyDOList.stream().collect(Collectors.groupingBy(BlnoVslvoyDO::getBlnoId));

            for (ConfirmNoticeRespVO resp : respList) {
                resp.setChecker(resp.getUpdater());
                resp.setCheckDate(resp.getUpdateTime());
                if (ExImTypeEnum.IM.getValue().equals(exImType)) {
                    BlnoVslvoyDO vslvoy = VslvoyUtil.getLastVslvoyAllTransMode(vslvoyListMap.get(resp.getBlnoId()));
                    resp.setVesselCode(vslvoy.getVesselCode());
                    resp.setVoyageIm(vslvoy.getVoyageIm());
                } else {
                    BlnoVslvoyDO vslvoy = VslvoyUtil.getFirstVslvoyAllTransMode(vslvoyListMap.get(resp.getBlnoId()));
                    resp.setVesselCode(vslvoy.getVesselCode());
                    resp.setVoyageIm(vslvoy.getVoyageEx());
                }
            }
        }

        return new PageResult<>(respList, pageResult.getTotal());
    }

    @Override
    public void noteConfirmNotice(Long id) {
        ConfirmNoticeDO confirmNotice = confirmNoticeMapper.selectById(id);
        checkConfirmNote(confirmNotice);
        confirmNoticeMapper.updateById(confirmNotice);
    }

    @Override
    public void noteBatchConfirmNotice(Collection<Long> ids) {
        List<ConfirmNoticeDO> confirmNoticeList = confirmNoticeMapper.selectBatchIds(ids);
        if (CollectionUtils.isEmpty(confirmNoticeList)) {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_EXISTS);
        }
        confirmNoticeList.forEach(this::checkConfirmNote);
        confirmNoticeMapper.updateBatch(confirmNoticeList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createConfirmNotice(List<Long> blnoIds, String noticeType, String noticeContent, String operateType) {
        List<BlnoDO> blnoDos =
                blnoMapper.selectList(new LambdaQueryWrapperX<BlnoDO>().inIfPresent(BlnoDO::getId, blnoIds).eq(BlnoDO::getValid, SiValidEnum.VALID.getValue()));
        if (CollectionUtil.isEmpty(blnoDos)) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        List<ConfirmNoticeDetailDO> confirmNoticeDetailDos = new ArrayList<>();
        blnoDos.forEach(n -> {
            ConfirmNoticeDO confirmNoticeDO = new ConfirmNoticeDO();
            confirmNoticeDO.setBlnoId(n.getId());
            confirmNoticeDO.setNotice(noticeType);
            confirmNoticeDO.setOperateType(operateType);
            confirmNoticeDO.setStatus(ConfirmNoticeStatusEnum.PENDING.getValue());
            confirmNoticeDO.setApplyUser(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
            confirmNoticeDO.setApplyTime(LocalDateTime.now());
            confirmNoticeDO.setDelCode(n.getDelCode());
            confirmNoticeDO.setPorCode(n.getPorCode());
            confirmNoticeDO.setPolCode(n.getPolCode());
            confirmNoticeDO.setPodCode(n.getPodCode());
            confirmNoticeDO.setNoticeContent(noticeContent);
            confirmNoticeMapper.insert(confirmNoticeDO);

            if (operateType.equals(ConfirmNoticeOperateEnum.APPROVAL.getValue())) {
                ConfirmNoticeDetailDO confirmNoticeDetailDO = new ConfirmNoticeDetailDO();
                confirmNoticeDetailDO.setNoticeId(confirmNoticeDO.getId());
                confirmNoticeDetailDO.setCurrentPort(confirmNoticeDO.getDelCode());
                confirmNoticeDetailDO.setNoticeStatus(ConfirmNoticeStatusEnum.PENDING.getValue());
                confirmNoticeDetailDos.add(confirmNoticeDetailDO);
            }
        });
        confirmNoticeDetailMapper.insertBatch(confirmNoticeDetailDos);
    }

    @Override
    public void createSingleNotice(Long blnoId, String operateType, String noticeType) {
        BlnoDO blnoDO = blnoMapper.selectById(blnoId);
        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        List<ConfirmNoticeDetailDO> confirmNoticeDetailDos = new ArrayList<>();
        ConfirmNoticeDO confirmNoticeDO = new ConfirmNoticeDO();
        confirmNoticeDO.setNotice(noticeType);
        confirmNoticeDO.setBlnoId(blnoDO.getId());
        confirmNoticeDO.setOperateType(operateType);
        confirmNoticeDO.setStatus(ConfirmNoticeStatusEnum.PENDING.getValue());
        confirmNoticeDO.setApplyUser(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        confirmNoticeDO.setApplyTime(LocalDateTime.now());
        confirmNoticeDO.setDelCode(blnoDO.getDelCode());
        confirmNoticeDO.setPorCode(blnoDO.getPorCode());
        confirmNoticeDO.setPolCode(blnoDO.getPolCode());
        confirmNoticeDO.setPodCode(blnoDO.getPodCode());
        confirmNoticeMapper.insert(confirmNoticeDO);
        // 如果 是cod mcn 装卸港口都需要审批
        if(needPort(noticeType,blnoDO.getPodCode())){
            Arrays.asList(blnoDO.getPolCode(),blnoDO.getPodCode()).forEach(
                    n->{
                        getConfirmNoticeDetailDos(confirmNoticeDO.getId(),n,confirmNoticeDetailDos);
                    }
            );
        }else{
            getConfirmNoticeDetailDos(confirmNoticeDO.getId(),blnoDO.getPolCode(),confirmNoticeDetailDos);
        }
        confirmNoticeDetailMapper.insertBatch(confirmNoticeDetailDos);
    }

    private void getConfirmNoticeDetailDos(Long id, String currentPort, List<ConfirmNoticeDetailDO> confirmNoticeDetailDos) {
        ConfirmNoticeDetailDO confirmNoticeDetailDO = new ConfirmNoticeDetailDO();
        confirmNoticeDetailDO.setNoticeId(id);
        confirmNoticeDetailDO.setCurrentPort(currentPort);
        confirmNoticeDetailDO.setNoticeStatus(ConfirmNoticeStatusEnum.PENDING.getValue());
        confirmNoticeDetailDos.add(confirmNoticeDetailDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveNotice(ConfirmNoticeRejectReqVO confirmNoticeRejectReqVO) {
        ConfirmNoticeDO confirmNotice = confirmNoticeMapper.selectById(confirmNoticeRejectReqVO.getId());
        if (confirmNotice == null) {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_EXISTS);
        }
        if (!ConfirmNoticeStatusEnum.PENDING.getValue().equals(confirmNotice.getStatus())) {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_NOTED);
        }
        confirmNoticeRejectReqVO.setNotice(confirmNotice.getNotice());
        String notice = confirmNotice.getNotice();
        Long noticeId = confirmNotice.getId();
        BlnoDO blnoDO = blnoMapper.selectById(confirmNotice.getBlnoId());
        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        if (ConfirmNoticeOperateEnum.APPROVAL.getValue().equals(confirmNotice.getOperateType())) {
            if (CANCEL_TELEX_SEAWAY.getValue().equals(notice)) {
                // 取消电放
                approveBlnoAndNotice(blnoDO, noticeId, NoticeRemarkConstants.APPROVE_CANCEL_TELEX_SEAWAY, confirmNoticeRejectReqVO,null);
            } else {
                approveBlnoAndNotice(blnoDO, noticeId, null, confirmNoticeRejectReqVO,confirmNoticeRejectReqVO.getResponse());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectNotice(ConfirmNoticeRejectReqVO noticeRejectReqVO) {
        ConfirmNoticeDO confirmNoticeDO = confirmNoticeMapper.selectById(noticeRejectReqVO.getId());
        if (confirmNoticeDO == null) {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_EXISTS);
        }
        if (!ConfirmNoticeStatusEnum.PENDING.getValue().equals(confirmNoticeDO.getStatus())) {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_NOTED);
        }
        BlnoDO blnoDO = blnoMapper.selectById(confirmNoticeDO.getBlnoId());
        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        LambdaUpdateWrapper<ConfirmNoticeDO> noticeWrapper = new LambdaUpdateWrapper<>();
        noticeWrapper.eq(ConfirmNoticeDO::getId, confirmNoticeDO.getId())
                .set(ConfirmNoticeDO::getStatus, ConfirmNoticeStatusEnum.REJECTED.getValue())
                .set(ConfirmNoticeDO::getUpdateTime, LocalDateTime.now())
        ;
        confirmNoticeMapper.update(null, noticeWrapper);
        noticeRejectReqVO.setNotice(confirmNoticeDO.getNotice());
        LambdaUpdateWrapper<ConfirmNoticeDetailDO> noticeDetailWrapper = new LambdaUpdateWrapper<ConfirmNoticeDetailDO>().eq(ConfirmNoticeDetailDO::getNoticeId,confirmNoticeDO.getId())
                .set(ConfirmNoticeDetailDO::getNoticeStatus,ConfirmNoticeStatusEnum.REJECTED.getValue())
                .set(ConfirmNoticeDetailDO::getApproveTime, LocalDateTime.now())
                .set(ConfirmNoticeDetailDO::getApproveUser,String.valueOf(SecurityFrameworkUtils.getLoginUserId()))
                .set(ConfirmNoticeDetailDO::getApproveRemark,noticeRejectReqVO.getApproveRemark())
                .set(ConfirmNoticeDetailDO::getApproveRemarkType,noticeRejectReqVO.getApproveRemarkType());
        if (needPort(noticeRejectReqVO.getNotice(),noticeRejectReqVO.getCurrentPort())) {
            if(confirmNoticeDetailMapper.selectCount(new LambdaQueryWrapperX<ConfirmNoticeDetailDO>()
                    .eq(ConfirmNoticeDetailDO::getNoticeId,confirmNoticeDO.getId())
                    .eq(ConfirmNoticeDetailDO::getCurrentPort,noticeRejectReqVO.getCurrentPort())
                    .ne(ConfirmNoticeDetailDO::getNoticeStatus,ConfirmNoticeStatusEnum.PENDING.getValue()))!=0){
                Assertion.assertTrue(false).raise0(ConfirmNoticeErrorCodeConstants.CONFIRM_CHANGE_PORT_APPROVED.getCode()
                        , noticeRejectReqVO.getCurrentPort());
            }
            noticeDetailWrapper.eq(ConfirmNoticeDetailDO::getCurrentPort,noticeRejectReqVO.getCurrentPort());
        }
        confirmNoticeDetailMapper.update(null,noticeDetailWrapper);

        if(!CANCEL_TELEX_SEAWAY.getValue().equals(noticeRejectReqVO.getNotice())){
            // 旧提单 如果审核拒绝 继续生效  si更改状态修改为拒绝
            LambdaUpdateWrapper<BlnoDO> lambdaUpdate = new LambdaUpdateWrapper<>();
            lambdaUpdate.eq(BlnoDO::getId, blnoDO.getOldBlnoId())
                    .set(BlnoDO::getSiChangeStatus, SiChangeStatusEnum.SI_AMENDMENT_REJECTED.getValue())
                    .set(BlnoDO::getUpdateTime, LocalDateTime.now());
            blnoMapper.update(null, lambdaUpdate);

            // 新提单 如果审核拒绝 则 有效标记修改为已失效
            LambdaUpdateWrapper<BlnoDO> lambdaUpdate1 = new LambdaUpdateWrapper<>();
            lambdaUpdate1.eq(BlnoDO::getId, blnoDO.getId())
                    .set(BlnoDO::getValid, SiValidEnum.UNVALID.getValue())
                    .set(BlnoDO::getUpdateTime, LocalDateTime.now());
            blnoMapper.update(null, lambdaUpdate1);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelNotice(Long id) {
        ConfirmNoticeDO confirmNoticeDO = confirmNoticeMapper.selectById(id);
        if (confirmNoticeDO == null) {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_EXISTS);
        }
        if (!ConfirmNoticeStatusEnum.PENDING.getValue().equals(confirmNoticeDO.getStatus())) {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_NOTED);
        }
        LambdaUpdateWrapper<ConfirmNoticeDO> noticeWrapper = new LambdaUpdateWrapper<>();
        noticeWrapper.eq(ConfirmNoticeDO::getId, id)
                .set(ConfirmNoticeDO::getStatus, ConfirmNoticeStatusEnum.CANCEL.getValue())
                .set(ConfirmNoticeDO::getUpdateTime, LocalDateTime.now())
        ;
        confirmNoticeMapper.update(null, noticeWrapper);

        BlnoDO blnoDO = blnoMapper.selectById(confirmNoticeDO.getBlnoId());
        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        // 作废新提单
        blnoMapper.update(null, new LambdaUpdateWrapper<BlnoDO>()
                .eq(BlnoDO::getId, confirmNoticeDO.getBlnoId())
                .set(BlnoDO::getValid, SiValidEnum.UNVALID.getValue()));
        //修改 原提单更改申请状态
        blnoMapper.update(null, new LambdaUpdateWrapper<BlnoDO>()
                .eq(BlnoDO::getId, blnoDO.getOldBlnoId())
                .set(BlnoDO::getSiChangeStatus, SiChangeStatusEnum.SI_AMENDMENT_CANCELLED_BY_CUSTOMER.getValue()));
    }

    private void approveBlnoAndNotice(BlnoDO blnoDO, Long noticeId, String remark, ConfirmNoticeRejectReqVO confirmNoticeRejectReqVO, HttpServletResponse response) {
        // 修改 提单签单类型
        if (CANCEL_TELEX_SEAWAY.getValue().equals(confirmNoticeRejectReqVO.getNotice())) {
            LambdaUpdateWrapper<BlnoDO> lambdaUpdate = new LambdaUpdateWrapper<>();
            lambdaUpdate.eq(BlnoDO::getId, blnoDO.getId())
                    .set(BlnoDO::getSignType, "")
                    .set(BlnoDO::getSiStatus, SiStatusEnum.BookingConfirmed.getValue())
                    .set(BlnoDO::getUpdateTime, LocalDateTime.now());
            blnoMapper.update(null, lambdaUpdate);
            saveOperateLogBatch(Arrays.asList(blnoDO.getId()), SI_OPERATE_TYPE, SiOperateTypeEnum.OPERATE_SI_NINE.getValue(),
                    SiOperateTypeEnum.OPERATE_SI_NINE.getLabel(), remark);
        }
        // 修改通知子记录
        LambdaUpdateWrapper<ConfirmNoticeDetailDO> noticeDetailWrapper = new LambdaUpdateWrapper<ConfirmNoticeDetailDO>()
                .eq(ConfirmNoticeDetailDO::getNoticeId, noticeId)
                .set(ConfirmNoticeDetailDO::getNoticeStatus, ConfirmNoticeStatusEnum.APPROVED.getValue())
                .set(ConfirmNoticeDetailDO::getApproveTime, LocalDateTime.now())
                .set(ConfirmNoticeDetailDO::getApproveUser, String.valueOf(SecurityFrameworkUtils.getLoginUserId()))
                .set(ConfirmNoticeDetailDO::getApproveRemark, confirmNoticeRejectReqVO.getApproveRemark())
                .set(ConfirmNoticeDetailDO::getApproveRemarkType, confirmNoticeRejectReqVO.getApproveRemarkType());
        if (needPort(confirmNoticeRejectReqVO.getNotice(), confirmNoticeRejectReqVO.getCurrentPort())) {
            if (confirmNoticeDetailMapper.selectCount(new LambdaQueryWrapperX<ConfirmNoticeDetailDO>()
                    .eq(ConfirmNoticeDetailDO::getNoticeId, noticeId)
                    .eq(ConfirmNoticeDetailDO::getCurrentPort, confirmNoticeRejectReqVO.getCurrentPort())
                    .ne(ConfirmNoticeDetailDO::getNoticeStatus, ConfirmNoticeStatusEnum.PENDING.getValue())) != 0) {
                Assertion.assertTrue(false).raise0(ConfirmNoticeErrorCodeConstants.CONFIRM_CHANGE_PORT_APPROVED.getCode()
                        , confirmNoticeRejectReqVO.getCurrentPort());
            }
            noticeDetailWrapper.eq(ConfirmNoticeDetailDO::getCurrentPort, confirmNoticeRejectReqVO.getCurrentPort());
        }
        // 修改通知主记录
        confirmNoticeDetailMapper.update(null, noticeDetailWrapper);
        // 只有 所有审批记录均为通过 主信息为通过
        List<ConfirmNoticeDetailDO> confirmNoticeDetailDos = confirmNoticeDetailMapper.selectList(
                new LambdaUpdateWrapper<ConfirmNoticeDetailDO>().eq(ConfirmNoticeDetailDO::getNoticeId, noticeId));

        Boolean approved = confirmNoticeDetailDos.stream().allMatch(n -> ConfirmNoticeStatusEnum.APPROVED.getValue().equals(n.getNoticeStatus()));
        LambdaUpdateWrapper<ConfirmNoticeDO> noticeWrapper = new LambdaUpdateWrapper<>();
        noticeWrapper.eq(ConfirmNoticeDO::getId, noticeId)
                .set(ConfirmNoticeDO::getStatus, approved ? ConfirmNoticeStatusEnum.APPROVED.getValue() : ConfirmNoticeStatusEnum.PENDING.getValue())
                .set(ConfirmNoticeDO::getUpdateTime, LocalDateTime.now());
        confirmNoticeMapper.update(null, noticeWrapper);

        // MCN COD 需要二审 所以 一次审核   原提单不一定失效  新提单不一定生效
        if (!CANCEL_TELEX_SEAWAY.getValue().equals(confirmNoticeRejectReqVO.getNotice())) {
            // 旧提单 如果审核通过 则失效
            LambdaUpdateWrapper<BlnoDO> lambdaUpdate = new LambdaUpdateWrapper<>();
            lambdaUpdate.eq(BlnoDO::getId, blnoDO.getOldBlnoId())
                    .set(BlnoDO::getValid, approved ? SiValidEnum.UNVALID.getValue() : SiValidEnum.VALID.getValue())
                    .set(BlnoDO::getSiChangeStatus, approved ? SI_AMENDMENT_APPROVED.getValue() : SI_AMENDMENT_PROCESSING.getValue())
                    .set(BlnoDO::getUpdateTime, LocalDateTime.now());
            blnoMapper.update(null, lambdaUpdate);

            // 新提单 如果审核通过 则生效
            LambdaUpdateWrapper<BlnoDO> lambdaUpdate1 = new LambdaUpdateWrapper<>();
            lambdaUpdate1.eq(BlnoDO::getId, blnoDO.getId())
                    .set(BlnoDO::getValid, approved ? SiValidEnum.VALID.getValue() : SiValidEnum.PENDING.getValue())
                    .set(BlnoDO::getUpdateTime, LocalDateTime.now())
                    .set(BlnoDO::getSiStatus, SiStatusEnum.BookingPendingSpace.getValue());
            blnoMapper.update(null, lambdaUpdate1);
            // 删除原提单的 打印记录
            blnoBlPrintLogMapper.delete(new LambdaQueryWrapper<BlnoBlPrintLogDO>().in(BlnoBlPrintLogDO::getBlnoId, Arrays.asList(blnoDO.getOldBlnoId(),blnoDO.getId())));
            // 同步so  特种货数据
            List<BlnoVslvoyDO> blnoVslvoyDos = blnoVslvoyMapper.selectList(new LambdaQueryWrapperX<BlnoVslvoyDO>().eq(BlnoVslvoyDO::getBlnoId, blnoDO.getId()));
            applySaveOrUpdateService.updateVoyageInfo(blnoDO.getBookingNo(), BeanUtil.copyToList(blnoVslvoyDos,BlnoVslvoyUpdateReqVO.class));
            soService.updateVoyageInfo(blnoDO.getBookingNo(),BeanUtil.copyToList(blnoVslvoyDos, SoVslvoyUpdateReqVO.class));
            // 异步打印draft 件  修改 提单状态为 待确认draft件
            taskExecutor.execute(() -> {
                blnoBlPrintLogService.printTheDraft(response, blnoDO.getId());
            });
        }
    }

    private boolean needPort(String notice,String currentPort){
        if (notice.equals(ConfirmNoticeEnum.MCN.getValue())
                || notice.equals(ConfirmNoticeEnum.COD.getValue())) {
            if (StringUtils.isEmpty(currentPort)) {
                throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOT_PORT);
            }else{
                return true ;
            }
        }
        return  false;
    }

    private void saveOperateLogBatch(List<Long> ids, String operateType, String operateTypeSub, String operateDesc,
                                     String remark) {
        List<BlnoDO> blnoDos = blnoMapper.selectList(new LambdaQueryWrapperX<BlnoDO>().in(BlnoDO::getId, ids).eq(BlnoDO::getValid, SiValidEnum.VALID.getValue()));
        Map<Long, String> stringMap = blnoDos.stream().collect(Collectors.toMap(BlnoDO::getId, BlnoDO::getBookingNo));
        List<OperateLogDO> operateLogDos = new ArrayList<>();
        ids.forEach(n -> {
            OperateLogDO operateLog = new OperateLogDO();
            operateLog.setBusinessId(n);
            operateLog.setBusinessNo(stringMap.get(n));
            operateLog.setBusinessType(BusinessTypeEnum.SI.getValue());
            operateLog.setOperateType(operateType);
            operateLog.setOperateTypeSub(operateTypeSub);
            operateLog.setOperateDesc(operateDesc);
            operateLog.setRemark(remark);
            operateLogDos.add(operateLog);
        });
        operateLogMapper.insertBatch(operateLogDos);
    }

    private void checkConfirmNote(ConfirmNoticeDO confirmNotice) {
        String notice = confirmNotice.getNotice();
        if (ConfirmNoticeEnum.FCN.getValue().equals(notice)
                || ConfirmNoticeEnum.TELEX_SEAWAY.getValue().equals(notice)) {
            String status = confirmNotice.getStatus();
            if (ConfirmNoticeStatusEnum.NOTED.getValue().equals(status)) {
                throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_EXIST_NOTICE);
            }
            confirmNotice.setStatus(ConfirmNoticeStatusEnum.NOTED.getValue());
            LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
            String userIdStr = null;
            if (loginUser != null && loginUser.getId() != null) {
                userIdStr = String.valueOf(loginUser.getId());
            }
            LocalDateTime now = LocalDateTime.now();
            confirmNotice.setUpdateTime(now);
            confirmNotice.setUpdater(userIdStr);
        } else {
            throw ServiceExceptionUtil.exception(ConfirmNoticeErrorCodeConstants.CONFIRM_NOTICE_NOTICE_ILLEGAL);
        }
    }

    private BlnoInfoVO getDetail(Long id) {
        BlnoInfoVO detail = new BlnoInfoVO();
        BlnoDO blnoDO = blnoMapper.selectById(id);
        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoErrorCodeConstants.BLNO_NOT_EXISTS);
        }
        detail.setBlnoBasInfoVO(BeanUtil.copyProperties(blnoDO, BlnoBaseInfoVO.class));
        detail.setBlnoVslvoys(
                BeanUtil.copyToList(blnoVslvoyService.getBlnoVslvoyBySiId(id), BlnoVslvoyUpdateReqVO.class));
        setVslvoyTerminal(detail.getBlnoVslvoys());
        vslvoyUtil.setEtaAndEtd(detail.getBlnoVslvoys());
        detail.setBlnoPayments(
                BeanUtil.copyToList(blnoPaymentService.getBlnoPaymentsByBlnoId(id), BlnoPaymentUpdateReqVO.class));
        detail.setReqVOList(
                BeanUtil.copyToList(requirementsService.getRequirementByBlnoId(id), RequirementsUpdateReqVO.class));
        detail.setAttachmentReqVO(BeanUtil.copyToList(attachmentService.seleteByReferenceId(id, SoSiEnum.SI.getValue()),
                AttachmentUpdateReqVO.class));
        detail.setBlnoScns(BeanUtil.copyToList(blnoScnService.getBlnoScnByBlnoId(id), BlnoScnUpdateReqVO.class));
        detail.setBlnoCargos(BeanUtil.copyToList(blnoCargoService.getBlnoCargoByBlnoId(id), BlnoCargoUpdateReqVO.class));
        detail.setBlnoCntrs(blnoCntrService.getBlnoCntrNewByBlnoId(id, blnoDO.getBkgVesselCode(), blnoDO.getBkgVoyage()));
        return detail;
    }

    private void setVslvoyTerminal(List<BlnoVslvoyUpdateReqVO> blnoVslvoyList) {
        if (CollectionUtil.isNotEmpty(blnoVslvoyList)) {
            blnoVslvoyList.forEach(e -> {
                List<String> vesselVoys = new ArrayList<>();
                vesselVoys.add(e.getVesselCode());
                Map<String, String> terminalMap = costChargeUtil.getVoyPortTerminalMap(e.getLaneCodeEx(), e.getVoyageEx(), e.getVesselCode());
                if (terminalMap != null) {
                    e.setPolTerminal(terminalMap.get(e.getPolCode()));
                    e.setPodTerminal(terminalMap.get(e.getPodCode()));
                }
            });
        }
    }
}
